home *** CD-ROM | disk | FTP | other *** search
/ CU Amiga Super CD-ROM 11 / CU Amiga Magazine's Super CD-ROM 11 (1997)(EMAP Images)(GB)(Track 1 of 3)[!][issue 1997-06].iso / cucd / programming / oberonv4 / source / system / locelems.mod (.txt) < prev    next >
Oberon Text  |  1996-04-28  |  17KB  |  412 lines

  1. Syntax10.Scn.Fnt
  2. Syntax10i.Scn.Fnt
  3. StampElems
  4. Alloc
  5. 28 Apr 96
  6. Syntax10b.Scn.Fnt
  7. FoldElems
  8. MODULE LocElems;    (** SHML, 4 Jan 96, 
  9. , based on PopupElems **)
  10.     (** Provide menu to locate positions in a text. As default, it searches for procedure headings.
  11.         Other search procedures for specific file extensions can be installed. *)
  12.     IMPORT
  13.         Oberon, Input, Display, Viewers, Files, Fonts, Printer,
  14.         Texts, TextFrames, MenuViewers, TextPrinter, Pictures, Amiga;
  15.     CONST
  16.         Ceres = FALSE;
  17.         VersionTag = 0X;
  18.         MenuDW = 3; MenuDH = 1;    (* margins of menu box *)
  19.         DUnit = TextFrames.Unit; PUnit = TextPrinter.Unit;
  20.         MR = 0; MM = 1; ML = 2; cancel = {ML, MM, MR};
  21.         white = Display.white; black = Display.black;
  22.         paint = Display.paint; replace = Display.replace; invert = Display.invert;
  23.         StrLen*= 64;
  24.         TableLen = 128;
  25.     TYPE
  26.         String = ARRAY StrLen OF CHAR;
  27.         Entry = RECORD
  28.             str: String;
  29.             pos: LONGINT
  30.         END;
  31.         Table = ARRAY TableLen OF Entry;
  32.         Elem*= POINTER TO ElemDesc;
  33.         ElemDesc = RECORD (Texts.ElemDesc)
  34.             name: ARRAY 32 OF CHAR;
  35.             n, width: INTEGER;    (* number of items, width *)
  36.             line: BOOLEAN;
  37.             stampLen: LONGINT;
  38.             t: Table
  39.         END;
  40.         SearchProc*= PROCEDURE(e: Elem; t: Texts.Text; VAR sort(*out*): BOOLEAN);
  41.         Element = POINTER TO ElementDesc;
  42.         ElementDesc = RECORD
  43.             ext: ARRAY 32 OF CHAR; search: SearchProc;
  44.             next: Element
  45.         END;
  46.     VAR wr: Texts.Writer; buf: Texts.Buffer; root: Element; defaultSearch: SearchProc; saveArea:Pictures.Picture;
  47.     PROCEDURE Str(s: ARRAY OF CHAR);    BEGIN Texts.WriteString(wr, s) END Str;
  48.     PROCEDURE Ln;    BEGIN Texts.WriteLn(wr) END Ln;
  49.     (* non_portable stuff *)    
  50.     PROCEDURE Save(X, Y, W, H: INTEGER);    (* copy from screen X, Y, W, H into save area *)
  51.     BEGIN
  52.         Pictures.Create(saveArea,W,H,Amiga.OberonDepth);
  53.         Pictures.CopyBlock(Display.screen,saveArea,X,Y,W,H,0,0,replace)
  54.     END Save;
  55.     PROCEDURE Restore(X, Y, W, H: INTEGER);    (* restore from save area to screen X, Y, W, H *)
  56.     BEGIN
  57.         Pictures.CopyBlock(saveArea,Display.screen,0,0,W,H,X,Y,replace)
  58.     END Restore;
  59.     (* auxiliary *)    
  60.     PROCEDURE Min(x, y: INTEGER): INTEGER;    BEGIN IF x < y THEN RETURN x ELSE RETURN y END END Min;
  61.     PROCEDURE Max(x, y: INTEGER): INTEGER;    BEGIN IF x > y THEN RETURN x ELSE RETURN y END END Max;
  62.     PROCEDURE StrDispWidth(fnt: Fonts.Font; s: ARRAY OF CHAR): LONGINT;
  63.         VAR pat: Display.Pattern; width, i, dx, x, y, w, h: INTEGER;
  64.     BEGIN
  65.         width := 0; i := 0;
  66.         WHILE s[i] # 0X DO Display.GetChar(fnt.raster, s[i], dx, x, y, w, h, pat); INC(width, dx); INC(i) END;
  67.         RETURN LONG(width)*DUnit
  68.     END StrDispWidth;
  69.     PROCEDURE DispStr(fnt: Fonts.Font; s: ARRAY OF CHAR; col, x0, y0: INTEGER);
  70.         VAR pat: Display.Pattern; i, dx, x, y, w, h: INTEGER;
  71.     BEGIN
  72.         i := 0;
  73.         WHILE s[i] # 0X DO
  74.             Display.GetChar(fnt.raster, s[i], dx, x, y, w, h, pat);
  75.             Display.CopyPattern(col, pat, x0+x, y0+y, paint);
  76.             INC(i); INC(x0, dx)
  77.         END
  78.     END DispStr;
  79.     (* change propagation *)    
  80.     PROCEDURE PrepareDraw(e: Elem; fnt: Fonts.Font; VAR dy: INTEGER);
  81.         VAR width, dh: INTEGER;
  82.     BEGIN
  83.         width := 0; dh := 0; dy := fnt.minY;
  84.         IF dy > -2 THEN dy := -2 END;
  85.         e.W := LONG(width)*DUnit+StrDispWidth(fnt, e.name)+DUnit; e.H := LONG(fnt.maxY-fnt.minY+dh)*DUnit
  86.     END PrepareDraw;
  87.     PROCEDURE Sort(e: Elem);
  88.         (* sort the array with insertion sort (because it's stable!) *)
  89.         VAR i, j: INTEGER; entry: Entry;
  90.     BEGIN
  91.         FOR j := 1 TO e.n-1 DO
  92.             entry := e.t[j];
  93.             i := j-1;
  94.             WHILE (i >= 0) & (entry.str < e.t[i].str) DO e.t[i+1] := e.t[i]; DEC(i) END;
  95.             e.t[i+1] := entry
  96.         END
  97.     END Sort;
  98.     PROCEDURE Append*(e: Elem; str: ARRAY OF CHAR; pos: LONGINT): BOOLEAN;
  99.         (** append str and pos to table in element e, return "table is full"; (LEN(str) <= StrLen, 100) *)
  100.     BEGIN
  101.         ASSERT(LEN(str) <= StrLen, 100);
  102.         IF e.n < TableLen THEN COPY(str, e.t[e.n].str); e.t[e.n].pos := pos; INC(e.n) END;
  103.         RETURN e.n = TableLen
  104.     END Append;
  105.     PROCEDURE DefaultSearch(e: Elem; t: Texts.Text; VAR sort(*out*): BOOLEAN);
  106.         VAR s: Texts.Scanner; str, type: ARRAY 32 OF CHAR; class, i, j: INTEGER;
  107.     BEGIN
  108.         Texts.OpenScanner(s, t, 0);
  109.         LOOP
  110.             WHILE ~s.eot & ((s.class # Texts.Name) OR (s.s # "PROCEDURE")) DO Texts.Scan(s) END;
  111.             IF s.eot THEN EXIT END;
  112.             (* s.s = PROCEDURE *)
  113.             type := "";
  114.             Texts.Scan(s);
  115.             IF ~((s.class = Texts.Char) & (s.c = "^")) THEN    (* ignore forward declarations *)
  116.                 IF s.class = Texts.Char THEN    (* ( *)
  117.                     IF s.c = "(" THEN
  118.                         REPEAT COPY(s.s, type); class := s.class; Texts.Scan(s)
  119.                         UNTIL s.eot OR (class = Texts.Name) & (s.class = Texts.Char) & (s.c = ")");
  120.                         IF s.eot THEN EXIT END
  121.                     END;
  122.                     Texts.Scan(s)
  123.                 END;
  124.                 IF s.class = Texts.Name THEN
  125.                     i := -1;
  126.                     IF type # "" THEN
  127.                         REPEAT INC(i); str[i] := type[i] UNTIL str[i] = 0X;
  128.                         str[i] := "."
  129.                     END;
  130.                     j := -1;
  131.                     REPEAT INC(j); INC(i); str[i] := s.s[j] UNTIL str[i] = 0X;
  132.                     IF Append(e, str, Texts.Pos(s)-1) THEN EXIT END
  133.                 END
  134.             END
  135.         END;
  136.         sort := TRUE
  137.     END DefaultSearch;
  138.     PROCEDURE Extension(name: ARRAY OF CHAR; VAR ext: ARRAY OF CHAR);
  139.         VAR i, j: INTEGER;
  140.     BEGIN
  141.         i := -1; REPEAT INC(i) UNTIL name[i] = 0X;
  142.         REPEAT DEC(i) UNTIL (name[i] = ".") OR (i = 0);
  143.         IF i = 0 THEN ext[0] := 0X
  144.         ELSE
  145.             j := -1; REPEAT INC(i); INC(j); ext[j] := name[i] UNTIL (name[i] = 0X) OR (name[i] = '"');
  146.             ext[j] := 0X
  147.         END
  148.     END Extension;
  149.     PROCEDURE Search(ext: ARRAY OF CHAR; VAR prev: Element): Element;
  150.         VAR l: Element;
  151.     BEGIN
  152.         l := root; prev := NIL;
  153.         WHILE (l # NIL) & (l.ext # ext) DO prev := l; l := l.next END;
  154.         RETURN l
  155.     END Search;
  156.     PROCEDURE Refresh(e: Elem; t: Texts.Text; menuFrame: Display.Frame);    (* generate menu text from t *)
  157.         VAR
  158.             s: Texts.Scanner; ext: ARRAY 32 OF CHAR; this, prev: Element;
  159.             i, j, width, n, dx, x, y, w, h: INTEGER; p: LONGINT; sort: BOOLEAN;
  160.     BEGIN
  161.         IF t # NIL THEN
  162.             e.n := 0; e.stampLen := t.len; sort := FALSE;
  163.             WITH menuFrame: TextFrames.Frame DO
  164.                 Texts.OpenScanner(s, menuFrame.text, 0); Texts.Scan(s);
  165.                 IF s.class IN {Texts.Name, Texts.String} THEN
  166.                     Extension(s.s, ext); this := Search(ext, prev);
  167.                     IF this # NIL THEN this.search(e, t, sort) ELSE defaultSearch(e, t, sort) END
  168.                 ELSE defaultSearch(e, t, sort)
  169.                 END
  170.             ELSE defaultSearch(e, t, sort)
  171.             END;
  172.             IF e.n > 0 THEN
  173.                 IF sort THEN Sort(e) END
  174.             ELSE e.t[0].str := "no items in text"; e.t[0].pos := -1; e.n := 1
  175.             END;
  176.             n := e.n;
  177.             WHILE n*Fonts.Default.height + 2*MenuDH + 4 > Oberon.DisplayHeight(0) DO DEC(n) END;
  178.             IF n < e.n THEN
  179.                 e.n := n;
  180.                 Str("too many procedures, not all will be shown!"); Ln;
  181.                 Texts.Append(Oberon.Log, wr.buf)
  182.             END;
  183.             e.width := 0;
  184.             FOR i := 0 TO n-1 DO
  185.                 j := 0; width := 0;
  186.                 WHILE e.t[i].str[j] # 0X DO
  187.                     Display.GetChar(Fonts.Default.raster, e.t[i].str[j], dx, x, y, w, h, p); INC(width, dx);
  188.                     INC(j)
  189.                 END;
  190.                 e.width := Max(e.width, width)
  191.             END
  192.         ELSE e.n := 0
  193.         END
  194.     END Refresh;
  195.     (* file input/output *)    
  196.     PROCEDURE Load(VAR r: Files.Rider; e: Elem);
  197.         VAR ch: CHAR;
  198.     BEGIN
  199.         Files.Read(r, ch);
  200.         IF ch = VersionTag THEN Files.ReadString(r, e.name); Files.ReadBool(r, e.line) END
  201.     END Load;
  202.     PROCEDURE Store(VAR r: Files.Rider; e: Elem);
  203.     BEGIN Files.Write(r, VersionTag); Files.WriteString(r, e.name); Files.WriteBool(r, e.line)
  204.     END Store;
  205.     (* graphics *)    
  206.     PROCEDURE Box(col, bkgnd, X, Y, W, H: INTEGER);
  207.     BEGIN
  208.         Display.ReplConst(col, X+1, Y+1, W-2, 1, replace);
  209.         Display.ReplConst(col, X+1, Y+H-2, W-2, 1, replace);
  210.         Display.ReplConst(col, X+1, Y+2, 1, H-4, replace);
  211.         Display.ReplConst(col, X+W-2, Y+2, 1, H-4, replace);
  212.         Display.ReplConst(col, X+4, Y, W-4, 1, replace);
  213.         Display.ReplConst(col, X+W-1, Y+1, 1, H-4, replace);
  214.         Display.ReplConst(bkgnd, X+2, Y+2, W-4, H-4, replace)
  215.     END Box;
  216.     PROCEDURE DrawElem(e: Elem; f: Display.Frame; pos: LONGINT; fnt: Fonts.Font; col, X, Y: INTEGER);
  217.         VAR beg: LONGINT; parc: TextFrames.Parc; bkgndCol: INTEGER;
  218.     BEGIN
  219.         IF f IS TextFrames.Frame THEN bkgndCol := f(TextFrames.Frame).col ELSE bkgndCol := black END;
  220.         TextFrames.ParcBefore(Texts.ElemBase(e), pos, parc, beg);
  221.         INC(Y, SHORT(parc.dsr DIV DUnit));
  222.         IF bkgndCol = col THEN col := ABS(white-col) END;
  223.         DispStr(fnt, e.name, col, X, Y);
  224.         IF e.line THEN Display.ReplPatternC(f, white, Display.grey1, X, Y-2, SHORT(e.W DIV DUnit), 1, X, Y-1, invert) END
  225.     END DrawElem;
  226.     PROCEDURE PrintElem(e: Elem; fnt: Fonts.Font; X, Y: INTEGER);
  227.     BEGIN
  228.         Printer.String(X, Y, e.name, fnt.name);
  229.         IF e.line THEN Printer.ReplConst(X, Y-2, SHORT((e.W-1) DIV PUnit), 1) END
  230.     END PrintElem;
  231.     PROCEDURE DrawMenu(e: Elem; X, Y, W, H: INTEGER);
  232.         VAR X0, dx, x, y, w, h, i, j: INTEGER; p: LONGINT;
  233.     BEGIN
  234.         Box(white, black, X, Y, W, H);
  235.         X0 := X+MenuDW+2; Y := Y+H-Fonts.Default.height-Fonts.Default.minY-MenuDH-2;
  236.         FOR i := 0 TO e.n-1 DO
  237.             j := 0; X := X0;
  238.             WHILE e.t[i].str[j] # 0X DO
  239.                 Display.GetChar(Fonts.Default.raster, e.t[i].str[j], dx, x, y, w, h, p);
  240.                 Display.CopyPattern(Display.white, p, X+x, Y+y, paint); INC(X, dx);
  241.                 INC(j)
  242.             END;
  243.             DEC(Y, Fonts.Default.height)
  244.         END
  245.     END DrawMenu;
  246.     (* actions *)    
  247.     PROCEDURE Show(e: Elem; X, Y: INTEGER; VAR cmd: INTEGER; VAR keySum: SET);
  248.         VAR
  249.             eH, W, H, w, newY, mx, my, top, bot, left, right, newCmd: INTEGER;
  250.             keys: SET;
  251.         PROCEDURE Flip(cmd: INTEGER);
  252.         BEGIN
  253.             IF cmd >= 0 THEN
  254.                 Display.ReplConst(white, left, top-(cmd+1)*Fonts.Default.height, right-left, Fonts.Default.height, invert)
  255.             END
  256.         END Flip;
  257.     BEGIN
  258.         eH := SHORT(e.H DIV DUnit);
  259.         Input.Mouse(keys, mx, my);
  260.         W := e.width + 2*MenuDW + 4; H := e.n*Fonts.Default.height + 2*MenuDH + 4;
  261.         IF (e.n = 0) OR (W > Oberon.DisplayWidth(X)) OR (H > Oberon.DisplayHeight(X)) THEN
  262.             IF e.n > 0 THEN Str("LocElem too big!"); Ln; Texts.Append(Oberon.Log, wr.buf) END;
  263.             REPEAT Input.Mouse(keys, mx, my); Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, mx, my)
  264.             UNTIL keys = {};
  265.             keySum := cancel; cmd := -1
  266.         ELSE
  267.             w := Oberon.DisplayWidth(X); left := Display.Left;
  268.             IF Ceres & (X >= Display.Left+Display.Width) THEN    (* adjust if on secondary *)
  269.                 INC(w, Display.Width); left := Display.Left+Display.Width
  270.             END;
  271.             X := Min(w-W, Max(mx-W DIV 2, left));    (* X >= left & X+W <= w *)
  272.             newY := my-((e.n-cmd)*Fonts.Default.height-Fonts.Default.height DIV 2);
  273.             IF (newY >= Display.Bottom) & (newY+H <= Oberon.DisplayHeight(X)) THEN    (* popup at mouse pos *)
  274.                 Y := newY
  275.             ELSE    (* drop down *)
  276.                 IF Y-H > Display.Bottom THEN Y := Y-H ELSE Y := Y+eH END;
  277.                 IF Y+H > Oberon.DisplayHeight(X) THEN Y := Display.Bottom END
  278.             END;
  279.             left := X+3; right := X+W-3; bot := Y+MenuDH+3; top := Y+H-MenuDH-2;
  280.             Oberon.RemoveMarks(X, Y, W, H); Oberon.FadeCursor(Oberon.Mouse);
  281.             Save(X, Y, W, H);    (* save background *)
  282.             DrawMenu(e, X, Y, W, H);
  283.             Flip(cmd); keySum := {};
  284.             REPEAT
  285.                 Input.Mouse(keys, mx, my); keySum := keySum+keys;
  286.                 Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, mx, my);
  287.                 IF keySum = cancel THEN cmd := -1
  288.                 ELSIF (mx >= left) & (mx <= right) & (my >= bot) & (my <= top) THEN
  289.                     newCmd := (top-my) DIV Fonts.Default.height;
  290.                     IF newCmd # cmd THEN Flip(cmd); Flip(newCmd); cmd := newCmd END
  291.                 ELSE Flip(cmd); cmd := -1
  292.                 END
  293.             UNTIL keys = {};
  294.             Oberon.FadeCursor(Oberon.Mouse);
  295.             Restore(X, Y, W, H)    (* restore background *)
  296.         END
  297.     END Show;
  298.     PROCEDURE Popup(e: Elem; msg: TextFrames.TrackMsg);
  299.         VAR
  300.             v: Viewers.Viewer; tf: TextFrames.Frame;
  301.             cmd: INTEGER; keys: SET;
  302.             beg, end: LONGINT;
  303.     BEGIN
  304.         v := Viewers.This(msg.frame.X, msg.frame.Y);
  305.         IF (v IS MenuViewers.Viewer) & (v.dsc = msg.frame) & (v.dsc.next IS TextFrames.Frame) THEN
  306.             tf := v.dsc.next(TextFrames.Frame);
  307.             IF tf.text.len # e.stampLen THEN Refresh(e, tf.text, msg.frame) END;
  308.             keys := msg.keys; cmd := 0;
  309.             Show(e, msg.X0, msg.Y0, cmd, keys);
  310.             IF keys = {MM, MR} THEN Refresh(e, tf.text, msg.frame)
  311.             ELSIF (keys # cancel) & (cmd > -1) & (e.t[cmd].pos >= 0) THEN
  312.                 beg := tf.org; end := TextFrames.Pos(tf, tf.X+tf.W, tf.Y);
  313.                 IF (e.t[cmd].pos < beg) OR (end <= e.t[cmd].pos) THEN TextFrames.Show(tf, e.t[cmd].pos) END;
  314.                 Oberon.PassFocus(v);
  315.                 TextFrames.SetCaret(tf, e.t[cmd].pos)
  316.             END
  317.         ELSE Str("LocElem not in menu viewer or content frame is not TextFrame"); Ln; Texts.Append(Oberon.Log, wr.buf)
  318.         END
  319.     END Popup;
  320.     (* element *)    
  321.     PROCEDURE Handle(e: Texts.Elem; VAR msg: Texts.ElemMsg);
  322.         VAR copy: Elem;
  323.     BEGIN
  324.         WITH e: Elem DO
  325.             IF msg IS TextFrames.DisplayMsg THEN
  326.                 WITH msg: TextFrames.DisplayMsg DO
  327.                     IF msg.prepare THEN PrepareDraw(e, msg.fnt, msg.Y0)
  328.                     ELSE DrawElem(e, msg.frame, msg.pos, msg.fnt, msg.col, msg.X0, msg.Y0)
  329.                     END
  330.                 END
  331.             ELSIF msg IS TextPrinter.PrintMsg THEN
  332.                 WITH msg: TextPrinter.PrintMsg DO
  333.                     IF ~msg.prepare THEN PrintElem(e, msg.fnt, msg.X0, msg.Y0) END
  334.                 END
  335.             ELSIF msg IS Texts.CopyMsg THEN
  336.                 WITH msg: Texts.CopyMsg DO
  337.                     NEW(copy); Texts.CopyElem(e, copy);
  338.                     copy.name := e.name; copy.line := e.line;
  339.                     msg.e := copy
  340.                 END
  341.             ELSIF msg IS Texts.IdentifyMsg THEN
  342.                 WITH msg: Texts.IdentifyMsg DO
  343.                     msg.mod := "LocElems"; msg.proc := "Alloc"
  344.                 END
  345.             ELSIF msg IS Texts.FileMsg THEN
  346.                 WITH msg: Texts.FileMsg DO
  347.                     IF msg.id = Texts.load THEN Load(msg.r, e)
  348.                     ELSIF msg.id = Texts.store THEN Store(msg.r, e)
  349.                     END
  350.                 END
  351.             ELSIF msg IS TextFrames.TrackMsg THEN
  352.                 WITH msg: TextFrames.TrackMsg DO Popup(e, msg) END
  353.             END
  354.         END
  355.     END Handle;
  356.     PROCEDURE Alloc*;
  357.         VAR e: Elem;
  358.     BEGIN NEW(e); e.handle := Handle; Texts.new := e
  359.     END Alloc;
  360.     (** commands **)    
  361.     PROCEDURE Insert*;
  362.         VAR e: Elem; ins: TextFrames.InsertElemMsg; s: Texts.Scanner;
  363.     BEGIN
  364.         NEW(e); e.line := TRUE;
  365.         Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s);
  366.         IF ~(s.class IN {Texts.Name, Texts.String}) OR (s.s[0] = 0X) THEN e.name := "Loc" ELSE COPY(s.s, e.name) END;
  367.         e.handle := Handle; ins.e := e; Viewers.Broadcast(ins)
  368.      END Insert;
  369.     PROCEDURE Rename*;
  370.         VAR e: Elem; text: Texts.Text; beg, end, time: LONGINT; r: Texts.Reader; s: Texts.Scanner;
  371.     BEGIN
  372.         Oberon.GetSelection(text, beg, end, time);
  373.         IF time >= 0 THEN
  374.             Texts.OpenReader(r, text, beg); Texts.ReadElem(r);
  375.             IF (r.elem # NIL) & (r.elem IS Elem) THEN
  376.                 e := r.elem(Elem);
  377.                 Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s);
  378.                 IF s.class = Texts.Name THEN
  379.                     COPY(s.s, e.name); text.notify(text, Texts.replace, Texts.ElemPos(e), Texts.ElemPos(e)+1)
  380.                 END
  381.             END
  382.         END
  383.      END Rename;
  384.      PROCEDURE Toggle*;
  385.          VAR e: Elem; text: Texts.Text; beg, end, time: LONGINT; r: Texts.Reader;
  386.     BEGIN
  387.          Oberon.GetSelection(text, beg, end, time);
  388.         IF time >= 0 THEN
  389.             Texts.OpenReader(r, text, beg); Texts.ReadElem(r);
  390.             IF (r.elem # NIL) & (r.elem IS Elem) THEN
  391.                 e := r.elem(Elem); e.line := ~e.line; text.notify(text, Texts.replace, Texts.ElemPos(e), Texts.ElemPos(e)+1)
  392.             END
  393.         END
  394.      END Toggle;
  395.     PROCEDURE Install*(ext: ARRAY OF CHAR; search: SearchProc);
  396.         VAR new, this, prev: Element;
  397.     BEGIN
  398.         IF ext = "*" THEN defaultSearch := search
  399.         ELSE
  400.             NEW(new); COPY(ext, new.ext); new.search := search;
  401.             this := Search(new.ext, prev);    (* check for duplicates *)
  402.             IF this = NIL THEN new.next := root; root := new    (* new entry *)
  403.             ELSIF this.search # new.search THEN    (* new entry for existing extension -> remove this *)
  404.                 IF this = root THEN new.next := root.next; root := new
  405.                 ELSE new.next := this.next; prev.next := new
  406.                 END
  407.             END
  408.         END
  409.     END Install;
  410. BEGIN NEW(buf); Texts.OpenBuf(buf); Texts.OpenWriter(wr); root := NIL; defaultSearch := DefaultSearch; NEW(saveArea)
  411. END LocElems.
  412.